package puzzle.akari.data;

import puzzle.common.data.AbstractBoard;
import puzzle.common.data.HatanException;
import puzzle.common.point.Direction;
import puzzle.common.point.Pnt;
import tomoto.util.ArrayUtil;

public class AkariBoard extends AbstractBoard<AkariBoard> {
	private static final int LIGHT_PUT_MASK = 1;
	private static final int LIGHT_FORBIDDEN_MASK = 2;
	private static final int LIT_MASK = 4;
	private static final int ASSUMED_MASK = 8;

	private int[] m_blockNumbers;
	private int[] m_stateFlags;
	private int m_numberOfDarkCells;
//	private Pair<boolean[]> m_dirtyFlagsPair;

	public AkariBoard(int w, int h) {
		super(w, h);
		int s = w * h;
		m_blockNumbers = new int[s];
		m_stateFlags = new int[s];
		m_numberOfDarkCells = 0;
//		m_dirtyFlagsPair = new Pair<boolean[]>(new boolean[s], new boolean[s]);
//		Arrays.fill(m_dirtyFlagsPair.car, true);
	}

	public AkariBoard(AkariBoard other) {
		super(other);
		m_blockNumbers = other.m_blockNumbers; // no need to dup
		m_stateFlags = ArrayUtil.dup(other.m_stateFlags);
		m_numberOfDarkCells = other.m_numberOfDarkCells;
//		m_dirtyFlagsPair = new Pair<boolean[]>(ArrayUtil.dup(other.m_dirtyFlagsPair.car), ArrayUtil
//				.dup(other.m_dirtyFlagsPair.cdr));
	}

	@Override
	protected void copyFromImpl(AkariBoard other) {
		m_blockNumbers = other.m_blockNumbers; // no need to dup
		ArrayUtil.copy(other.m_stateFlags, m_stateFlags);
		m_numberOfDarkCells = other.m_numberOfDarkCells;
//		ArrayUtil.copy(other.m_dirtyFlagsPair.car, m_dirtyFlagsPair.car);
//		ArrayUtil.copy(other.m_dirtyFlagsPair.cdr, m_dirtyFlagsPair.cdr);
	}

	@Override
	public AkariBoard dup() {
		return new AkariBoard(this);
	}

	@Override
	public void snapshotDirtyFlags() {
//		m_dirtyFlagsPair.swap();
//		Arrays.fill(m_dirtyFlagsPair.cdr, false);
	}
	
	public int getNumberOfDarkCells() {
		return m_numberOfDarkCells;
	}

	public int getBlockNumber(int x, int y) {
		if (inRange(x, y)) {
			return m_blockNumbers[x + y * width];
		} else {
			return BlockNumber.UNNUMBERED_BLOCK;
		}
	}

	private int getStateFlag(int x, int y) {
		if (inRange(x, y)) {
			return m_stateFlags[x + y * width];
		} else {
			return LIGHT_FORBIDDEN_MASK;
		}
	}

	public boolean isLightPut(int x, int y) {
		return (getStateFlag(x, y) & LIGHT_PUT_MASK) != 0;
	}

	public boolean isLightForbidden(int x, int y) {
		return (getStateFlag(x, y) & (LIGHT_FORBIDDEN_MASK | LIT_MASK)) != 0;
	}
	
	public boolean isLightExplicitlyForbidden(int x, int y) {
		return (getStateFlag(x, y) & LIGHT_FORBIDDEN_MASK) != 0;
	}

	public boolean isLit(int x, int y) {
		return (getStateFlag(x, y) & LIT_MASK) != 0;
	}

//	public boolean isDirty(int x, int y) {
//		return m_dirtyFlagsPair.car[x + y * width];
//	}

	public void setBlockNumber(int x, int y, int n) {
		m_blockNumbers[x + y * width] = n;
		if (BlockNumber.isBlock(n)) {
			setStateFlag(x, y, LIGHT_FORBIDDEN_MASK);
		} else {
			m_numberOfDarkCells++;
		}
	}

	public boolean setLightPut(Pnt p) throws HatanException {
		return setLightPut(p.x, p.y);
	}

	public boolean setLightPut(int x, int y) throws HatanException {
		if (isLightForbidden(x, y)) {
			throw new HatanException();
		}

		if (!setStateFlag(x, y, LIGHT_PUT_MASK)) {
			return false;
		} else {
			m_numberOfDarkCells--;
			setLitAround(x, y);
			return true;
		}
	}

	private boolean setLit(int x, int y) throws HatanException {
		if (isLightPut(x, y)) {
			throw new HatanException();
		}
		
		if (!setStateFlag(x, y, LIT_MASK)) {
			return false;
		} else {
			m_numberOfDarkCells--;
			return true;
		}
	}

	private boolean setLitAround(int x0, int y0) throws HatanException {
		boolean isChanged = false;
		for (Direction d : Direction.CLOCKWISE4) {
			int x = x0 + d.v.dx;
			int y = y0 + d.v.dy;
			while (BlockNumber.isNonBlock(getBlockNumber(x, y))) {
				isChanged |= setLit(x, y);
				x += d.v.dx;
				y += d.v.dy;
			}
		}
		return isChanged;
	}

	public boolean setLightForbidden(int x, int y) throws HatanException {
		if (isLightPut(x, y)) {
			throw new HatanException();
		}
		return setStateFlag(x, y, LIGHT_FORBIDDEN_MASK);
	}

	private boolean setStateFlag(int x, int y, int flag) {
		if ((getStateFlag(x, y) & flag) != 0) {
			// already set
			return false;
		} else {
			m_stateFlags[x + y * width] |= flag;
//			m_dirtyFlagsPair.cdr[x + y * width] = true;
			return true;
		}
	}

	public boolean isLightForbidden(Pnt p) {
		return isLightForbidden(p.x, p.y);
	}

	public boolean isLightPut(Pnt p) {
		return isLightPut(p.x, p.y);
	}

	public boolean canLightBePut(int x, int y) {
		return (getStateFlag(x, y) & (LIGHT_FORBIDDEN_MASK | LIGHT_PUT_MASK | LIT_MASK)) == 0; 
	}

	public boolean canLightBePut(Pnt p) {
		return canLightBePut(p.x, p.y);
	}

	public boolean setLightForbidden(Pnt p) throws HatanException {
		return setLightForbidden(p.x, p.y);
	}

	public boolean isAssumed(int x, int y) {
		return (getStateFlag(x, y) & ASSUMED_MASK) != 0;
	}
	
	public boolean setAssumed(int x, int y) {
		return setStateFlag(x, y, ASSUMED_MASK);
	}

	public boolean setAssumed(Pnt p) {
		return setAssumed(p.x, p.y);
	}

}
